package cn.jx.cjm.common.aop.advice;

import cn.jx.cjm.common.aop.annotation.Lockable;
import cn.jx.cjm.common.contants.CommonCache;
import cn.jx.cjm.common.enums.BizExceptionEnum;
import cn.jx.cjm.common.exception.BizException;
import cn.jx.cjm.common.util.LockUtils;
import cn.jx.cjm.common.util.SpringUtils;
import cn.jx.cjm.common.aop.annotation.LockerService;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.util.StringUtils;

import java.lang.reflect.Method;

/**
 * <p>
 *
 * </p>
 *
 * @author ：陈加敏 right_way@foxmail.com
 * @since ：19/12/11 0:33
 */
@Aspect
@Configuration
public class LockerInterceptor {

    @Autowired
    private LockerService lockerService;
    @Autowired
    private LockUtils lockUtils;

    @Pointcut(value = "@annotation(cn.jx.cjm.common.aop.annotation.Lockable)")
    public void pointCut() {
    }

    @Before("pointCut()")
    public void beforeLock(JoinPoint point) {
        Object[] arguments = point.getArgs();

        MethodSignature signature = (MethodSignature) point.getSignature();
        Method method = signature.getMethod();
        Lockable lockable = method.getAnnotation(Lockable.class);

        String key;
        if (StringUtils.hasText(lockable.key())) {
            key = lockable.key();
            LocalVariableTableParameterNameDiscoverer u = new LocalVariableTableParameterNameDiscoverer();
            String[] paramNames = u.getParameterNames(method);
            if (null != paramNames) {
                ExpressionParser parser = new SpelExpressionParser();
                Expression expression = parser.parseExpression(key);
                EvaluationContext context = new StandardEvaluationContext();
                for (int i = 0; i < arguments.length; i++) {
                    context.setVariable(paramNames[i], arguments[i]);
                }
                key = lockable.lockName() + expression.getValue(context, String.class);
            }
        } else {
            KeyGenerator keyGenerator = SpringUtils.getBean(CommonCache.KEY_GENERATOR_BEAN, KeyGenerator.class);
            key = lockable.lockName() + keyGenerator.generate(point.getTarget(), method, point.getArgs()).toString();
        }
        boolean lockSuccess;
        LockerService.LOCK_KEY.set(key);
        if (lockable.waitTime() > 0) {
            if (lockable.fail()) {
                lockSuccess = lockerService.fairLock(key, lockable.lockTimeUnit(), lockable.waitTime(), lockable.lockTime());
            } else {
                lockSuccess = lockerService.tryLock(key, lockable.lockTimeUnit(), lockable.waitTime(), lockable.lockTime());
            }
        } else {
            lockSuccess = lockerService.tryLock(key);
        }
        if (!lockSuccess) {
            throw new BizException(BizExceptionEnum.ERROR.getCode(), lockable.message());
        }

    }

    @Around("pointCut()")
    public Object aroundLock(ProceedingJoinPoint point) throws Throwable {
        Object result;
        try {
            result = point.proceed();
        } finally {
            lockUtils.removeLock();
        }

        return result;
    }
}
